#define DEBUG_LOGGING
using UnityEngine;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using System;
using System.Globalization;
using SQP;
using System.Reflection;
using System.Linq;
using Unity.DebugDisplay;
using Unity.Sample.Core;
using UnityEngine.Profiling;
#if UNITY_EDITOR
using UnityEditorInternal;
using UnityEditor;
using Unity.NetCode.Editor;
#endif





public class EnumeratedArrayAttribute : PropertyAttribute
{
    public readonly string[] names;
    public EnumeratedArrayAttribute(Type enumtype)
    {
        names = Enum.GetNames(enumtype);
    }
}



[ExecuteAlways]
[DisableAutoCreation]
public class ControlledEntityCameraUpdate : ManualComponentSystemGroup
{
    protected override void OnUpdate()
    {
        Profiler.BeginSample("ControlledEntityCameraUpdate");
        base.OnUpdate();
        Profiler.EndSample();
    }
}



[DefaultExecutionOrder(-1000)]
public class Game : MonoBehaviour
{
    public delegate void UpdateDelegate();

    public WeakAssetReference movableBoxPrototype;
    public WeakAssetReference dotsNetCodePrefabs;

    [AssetType(typeof(SoundDef))]
    public WeakAssetReference soundTrackTest;

    // Color scheme configurable? (cvars?)
    public enum GameColor
    {
        Friend,
        Enemy
    }
    [EnumeratedArray(typeof(GameColor))]
    public Color[] gameColors;

    public GameStatistics m_GameStatistics { get; private set; }



    public interface IGameLoop
    {
        bool Init(string[] args);
        void Shutdown();

        void Update();
        void FixedUpdate();
        void LateUpdate();
    }

    public static Game game;
    public event UpdateDelegate endUpdateEvent;

    // Vars owned by server and replicated to clients
    [ConfigVar(Name = "server.tickrate", DefaultValue = "60", Description = "Tickrate for server", Flags = ConfigVar.Flags.ServerInfo)]
    public static ConfigVar serverTickRate;

//    [ConfigVar(Name = "config.fov", DefaultValue = "60", Description = "Field of view", Flags = ConfigVar.Flags.Save)]
//    public static ConfigVar configFov;

    [ConfigVar(Name = "debug.catchloop", DefaultValue = "1", Description = "Catch exceptions in gameloop and pause game", Flags = ConfigVar.Flags.None)]
    public static ConfigVar debugCatchLoop;

    [ConfigVar(Name = "chartype", DefaultValue = "-1", Description = "Character to start with (-1 uses default character)")]
    public static ConfigVar characterType;

    [ConfigVar(Name = "allowcharchange", DefaultValue = "1", Description = "Is changing character allowed")]
    public static ConfigVar allowCharChange;

    [ConfigVar(Name = "debug.cpuprofile", DefaultValue = "0", Description = "Profile and dump cpu usage")]
    public static ConfigVar debugCpuProfile;

    [ConfigVar(Name = "net.dropevents", DefaultValue = "0", Description = "Drops a fraction of all packages containing events!!")]
    public static ConfigVar netDropEvents;

    [ConfigVar(Name = "show.entities", DefaultValue = "0", Description = "Entity stats")]
    public static ConfigVar showEntities;

    static readonly string k_UserConfigFilename = "user.cfg";
    public static readonly string k_BootConfigFilename = "boot.cfg";

    public UnityEngine.Audio.AudioMixer audioMixer;
    public Camera bootCamera;

    public LevelManager levelManager;
    public SQPClient sqpClient;

    public static double frameTime;
    public static int frameCount;

    public static bool IsHeadless()
    {
        return game.m_isHeadless;
    }

    public static int GameLoopCount
    {
        get { return game == null ? 0 : 1; }
    }

    public static T GetGameLoop<T>() where T : class
    {
        if (game == null)
            return null;
        foreach (var gameLoop in game.m_gameLoops)
        {
            T result = gameLoop as T;
            if (result != null)
                return result;
        }
        return null;
    }

    public static System.Diagnostics.Stopwatch Clock
    {
        get { return game.m_Clock; }
    }

    public string buildId
    {
        get { return _buildId; }
    }
    string _buildId = "NoBuild";

    public string buildUnityVersion
    {
        get { return _buildUnityVersion; }
    }
    // Start with sensible default, but we would like to have the full build version
    // which is only available in editor so we bake it into the build.
    string _buildUnityVersion = Application.unityVersion;

    public void RequestGameLoop(System.Type type, string[] args)
    {
        GameDebug.Assert(typeof(IGameLoop).IsAssignableFrom(type));

        m_RequestedGameLoopTypes.Add(type);
        m_RequestedGameLoopArguments.Add(args);
        GameDebug.Log("Game loop " + type + " requested");
    }



    // Pick argument for argument(!). Given list of args return null if option is
    // not found. Return argument following option if found or empty string if none given.
    // Options are expected to be prefixed with + or -
    public static string ArgumentForOption(List<string> args, string option)
    {
        var idx = args.IndexOf(option);
        if (idx < 0)
            return null;
        if (idx < args.Count - 1)
            return args[idx + 1];
        return "";
    }



    /*
     * THIS CAUSES RANDOM CRASHES
     *
    private static void PreLoadAnimationNodes()
    {
                // In Editor, convince Burst to JIT compile all Animation UNode types immediately.
#if UNITY_EDITOR
                var start = UnityEngine.Time.realtimeSinceStartup;
                var wasSyncCompile = Menu.GetChecked("Jobs/Burst/Synchronous Compilation");
                Menu.SetChecked("Jobs/Burst/Synchronous Compilation", true);
                try
                {
                    var animAssembly = typeof(Unity.Animation.AnimationGraphSystem).Assembly;
                    var unodeNonGenericTypes = animAssembly.GetTypes().Where(t =>
                        t.IsClass && !t.IsAbstract && !t.IsGenericType &&
                        typeof(Unity.DataFlowGraph.INodeFunctionality).IsAssignableFrom(t));
                    var unodeGenericTypesReferencedInData = animAssembly.GetTypes()
                        .Where(t => !t.IsClass && typeof(Unity.DataFlowGraph.INodeData).IsAssignableFrom(t))
                        .Select(t => t
                            .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                            .Select(fi => fi.FieldType)
                            .Where(ft => ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(Unity.DataFlowGraph.NodeHandle<>))
                            .Select(ft => ft.GetGenericArguments()[0])
                            .Where(ft => ft.IsGenericType))
                        .SelectMany(t => t)
                        .Distinct();

                    var nodeSet = new Unity.DataFlowGraph.NodeSet();

                    MethodInfo getFunctionality = nodeSet.GetType().GetMethod("GetFunctionality", new System.Type[0]);
                    foreach (System.Type t in unodeNonGenericTypes.Concat(unodeGenericTypesReferencedInData))
                        getFunctionality.MakeGenericMethod(t).Invoke(nodeSet, null);

                    nodeSet.Dispose();

                    Debug.Log($"Compilation of Animation UNode types took {UnityEngine.Time.realtimeSinceStartup - start} seconds");
                }
                finally
                {
                    Menu.SetChecked("Jobs/Burst/Synchronous Compilation", wasSyncCompile);
                }
#endif
    }
    */

    public void Awake()
    {
        GameDebug.Assert(game == null);
        DontDestroyOnLoad(gameObject);
        game = this;

        GameApp.IsInitialized = true;

        //PreLoadAnimationNodes();

        m_StopwatchFrequency = System.Diagnostics.Stopwatch.Frequency;
        m_Clock = new System.Diagnostics.Stopwatch();
        m_Clock.Start();

#if UNITY_EDITOR
        _buildUnityVersion = InternalEditorUtility.GetFullUnityVersion();
#endif
        var buildInfo = FindObjectOfType<BuildInfo>();
        if (buildInfo != null)
        {
            _buildId = buildInfo.buildId;
            _buildUnityVersion = buildInfo.buildUnityVersion;
        }

        var commandLineArgs = new List<string>(System.Environment.GetCommandLineArgs());

        // TODO we should only initialize this if we have a graphics device (i.e. non-headless)
        Overlay.Managed.Initialize();

#if UNITY_STANDALONE_LINUX
        m_isHeadless = true;
#else
        m_isHeadless = commandLineArgs.Contains("-batchmode");
#endif
        var noconsole = commandLineArgs.Contains("-noconsole");
        var consoleRestoreFocus = commandLineArgs.Contains("-consolerestorefocus");
        if (noconsole)
        {
            UnityEngine.Debug.Log("WARNING: starting without a console");
            var consoleUI = new ConsoleNullUI();
            Console.Init(buildId,buildUnityVersion, consoleUI);
        }else if (m_isHeadless)
        {
#if UNITY_EDITOR
            Debug.LogError("ERROR: Headless mode not supported in editor");
#endif

#if UNITY_STANDALONE_WIN
            string consoleTitle;

            var overrideTitle = ArgumentForOption(commandLineArgs, "-title");
            if (overrideTitle != null)
                consoleTitle = overrideTitle;
            else
                consoleTitle = Application.productName + " Console";

            consoleTitle += " [" + System.Diagnostics.Process.GetCurrentProcess().Id + "]";

            var consoleUI = new ConsoleTextWin(consoleTitle, consoleRestoreFocus);
#elif UNITY_STANDALONE_LINUX
            var consoleUI = new ConsoleTextLinux();
#else
            UnityEngine.Debug.Log("WARNING: starting without a console");
            var consoleUI = new ConsoleNullUI();
#endif
            Console.Init(buildId,buildUnityVersion,consoleUI);
        }
        else
        {
            var consoleUI = Instantiate(Resources.Load<ConsoleGUI>("Prefabs/ConsoleGUI"));
            DontDestroyOnLoad(consoleUI);
            Console.Init(buildId,buildUnityVersion,consoleUI);

            m_DebugOverlay = Instantiate(Resources.Load<DebugOverlay>("DebugOverlay"));
            DontDestroyOnLoad(m_DebugOverlay);
            m_DebugOverlay.Init();

            m_GameStatistics = new GameStatistics();
        }

        // If -logfile was passed, we try to put our own logs next to the engine's logfile
        // if -logfile was set to "-" we forward our logs to Debug.Log, so that it ends up on stdout.
        var engineLogFileLocation = ".";
        var logName = m_isHeadless ? "game_" + DateTime.UtcNow.ToString("yyyyMMdd_HHmmss_fff") : "game";
        var logfileArgIdx = commandLineArgs.IndexOf("-logfile");
        var forceForwardToDebug = false;
        if (logfileArgIdx >= 0 && commandLineArgs.Count >= logfileArgIdx)
        {
            var logFile = commandLineArgs[logfileArgIdx + 1];
            if (logFile == "-")
                forceForwardToDebug = true;
            else
                engineLogFileLocation = System.IO.Path.GetDirectoryName(logFile);
        }
        GameDebug.Init(engineLogFileLocation, logName, forceForwardToDebug);

        ConfigVar.Init();

        // Support -port and -query_port as per Multiplay standard
        var serverPort = ArgumentForOption(commandLineArgs, "-port");
        if (serverPort != null)
            Console.EnqueueCommandNoHistory("server.port " + serverPort);

        var sqpPort = ArgumentForOption(commandLineArgs, "-query_port");
        if (sqpPort != null)
            Console.EnqueueCommandNoHistory("server.sqp_port " + sqpPort);

        Console.EnqueueCommandNoHistory("exec -s " + k_UserConfigFilename);

        // Default is to allow no frame cap, i.e. as fast as possible if vsync is disabled
        Application.targetFrameRate = -1;

        if (m_isHeadless)
        {
            Application.targetFrameRate = serverTickRate.IntValue;
            QualitySettings.vSyncCount = 0; // Needed to make targetFramerate work; even in headless mode

#if !UNITY_STANDALONE_LINUX
            if (!commandLineArgs.Contains("-nographics"))
                GameDebug.Log("WARNING: running -batchmod without -nographics");
#endif
        }
        else
        {
            RenderSettings.Init();
        }

        // Out of the box game behaviour is driven by boot.cfg unless you ask it not to
        if (!commandLineArgs.Contains("-noboot"))
        {
            Console.EnqueueCommandNoHistory("exec -s " + k_BootConfigFilename);
        }

        if (m_isHeadless)
        {
            SoundSystem.Initialize(new SoundSystemNull());
        }
        else
        {
            var soundSystem = new SoundSystemBase();
            soundSystem.Init(audioMixer);
            SoundSystem.Initialize(soundSystem);

            GameObject go = (GameObject)GameObject.Instantiate(Resources.Load("Prefabs/ClientFrontend", typeof(GameObject)));
            UnityEngine.Object.DontDestroyOnLoad(go);
            clientFrontend = go.GetComponentInChildren<ClientFrontend>();
        }

        sqpClient = new SQP.SQPClient();

        GameDebug.Log("A2 initialized");
#if UNITY_EDITOR
        GameDebug.Log("Build type: editor");
#elif DEVELOPMENT_BUILD
        GameDebug.Log("Build type: development");
#else
        GameDebug.Log("Build type: release");
#endif
        GameDebug.Log("BuildID: " + buildId);
        GameDebug.Log("Unity: " + buildUnityVersion);
        GameDebug.Log("Cwd: " + System.IO.Directory.GetCurrentDirectory());

        levelManager = new LevelManager();
        levelManager.Init();
        GameDebug.Log("LevelManager initialized");

        GameDebug.Log("InputSystem initialized");

        // Game loops
        Console.AddCommand("serve", CmdServe, "Start server listening");
        Console.AddCommand("client", CmdClient, "client: Enter client mode.");
        Console.AddCommand("thinclient", CmdThinClient, "client: Enter thin client mode.");
        Console.AddCommand("boot", CmdBoot, "Go back to boot loop");
        Console.AddCommand("connect", CmdConnect, "connect <ip>: Connect to server on ip (default: localhost)");

        Console.AddCommand("menu", CmdMenu, "show the main menu");
        Console.AddCommand("load", CmdLoad, "Load level");
        Console.AddCommand("quit", CmdQuit, "Quits");
        Console.AddCommand("screenshot", CmdScreenshot, "Capture screenshot. Optional argument is destination folder or filename.");
        Console.AddCommand("crashme", (string[] args) => { GameDebug.Assert(false); }, "Crashes the game next frame ");
        Console.AddCommand("saveconfig", CmdSaveConfig, "Save the user config variables");
        Console.AddCommand("loadconfig", CmdLoadConfig, "Load the user config variables");

        Console.AddCommand("profile", CmdProfile, "Run the profiling for a level");

#if UNITY_STANDALONE_WIN
        Console.AddCommand("windowpos", CmdWindowPosition, "Position of window. e.g. windowpos 100,100");
#endif

        Console.SetOpen(true);
        Console.ProcessCommandLineArguments(commandLineArgs.ToArray());
#if UNITY_IOS
        // (marton) This is a hack to work around command line arguments not working on iOS
        if (!Application.isEditor)
            Console.EnqueueCommandNoHistory("preview Level_00");
#endif



        GameApp.CameraStack.OnCameraEnabledChanged += OnCameraEnabledChanged;
        GameApp.CameraStack.PushCamera(bootCamera);
    }

    void OnDisable()
    {
        GameDebug.Shutdown();
        Overlay.Managed.DoShutdown();
        Console.Shutdown();

        game = null;
        GameApp.IsInitialized = false;

        InputSystem.SetMousePointerLock(false);
        GameDebug.Log("A2 was shutdown");
    }

    bool pipeSetup = false;
    public void Update()
    {
        if (!m_isHeadless)
            RenderSettings.Update();

        // TODO (petera) remove this hack once we know exactly when renderer is available...
        if (!pipeSetup)
        {
            var hdpipe = RenderPipelineManager.currentPipeline as HDRenderPipeline;
            if (hdpipe != null)
            {
                var layer = LayerMask.NameToLayer("PostProcess Volumes");
                if (layer == -1)
                    GameDebug.LogWarning("Unable to find layer mask for camera fader");
                else
                {
                    var gameObject = new GameObject()
                    {
                        name = "Game Quick Volume",
                        layer = layer,
                        hideFlags = HideFlags.HideAndDontSave
                    };

                    m_ExposureVolume = gameObject.AddComponent<Volume>();
                    m_ExposureVolume.priority = 100.0f;
                    m_ExposureVolume.isGlobal = true;
                    var profile = m_ExposureVolume.profile;

                    m_Exposure = profile.Add<Exposure>();
                    m_Exposure.active = false;
                    m_Exposure.mode.Override(ExposureMode.Automatic);
                    m_Exposure.compensation.Override(0);
                }

                pipeSetup = true;
            }
        }
        if (m_ExposureReleaseCount > 0)
        {
            m_ExposureReleaseCount--;
            if (m_ExposureReleaseCount == 0)
                BlackFade(false);
        }

        GameApp.CameraStack.Update();


#if UNITY_EDITOR
        // Ugly hack to force focus to game view when using scriptable renderloops.
        if (Time.frameCount < 4)
        {
            try
            {
                var gameViewType = typeof(UnityEditor.EditorWindow).Assembly.GetType("UnityEditor.GameView");
                var gameView = (EditorWindow)Resources.FindObjectsOfTypeAll(gameViewType)[0];
                gameView.Focus();
            }
            catch (System.Exception) { /* too bad */ }
        }
#endif

        frameTime = (double)m_Clock.ElapsedTicks / m_StopwatchFrequency;
        frameCount = Time.frameCount;

        GameDebug.SetFrameCount(frameCount);
        Console.SetFrameTime(frameTime);;

        // Switch game loop if needed
        if (m_RequestedGameLoopTypes.Count > 0)
        {
            // Multiple running gameloops only allowed in editor
#if !UNITY_EDITOR
            ShutdownGameLoops();
#endif
            bool initSucceeded = false;
            for (int i = 0; i < m_RequestedGameLoopTypes.Count; i++)
            {
                try
                {
                    IGameLoop gameLoop = (IGameLoop)System.Activator.CreateInstance(m_RequestedGameLoopTypes[i]);
                    initSucceeded = gameLoop.Init(m_RequestedGameLoopArguments[i]);
                    Debug.Log("Game initialization succeeded: " + initSucceeded);
                    if (!initSucceeded)
                        break;

                    m_gameLoops.Add(gameLoop);
                }
                catch (System.Exception e)
                {
                    GameDebug.LogError(string.Format("Game loop initialization threw exception : ({0})\n{1}", e.Message, e.StackTrace));
                }
            }


            if (!initSucceeded)
            {
                ShutdownGameLoops();

                GameDebug.LogError("Game loop initialization failed ... reverting to boot loop");
            }

            m_RequestedGameLoopTypes.Clear();
            m_RequestedGameLoopArguments.Clear();
        }

        try
        {
            if (!m_ErrorState)
            {
                foreach (var gameLoop in m_gameLoops)
                {
                    gameLoop.Update();
                }
                levelManager.Update();
            }
        }
        catch (System.Exception e)
        {
            HandleGameloopException(e);
            throw;
        }

        if (SoundSystem.Instance != null)
            SoundSystem.Instance.Update();

        if (clientFrontend != null)
            clientFrontend.UpdateGame();

        Console.ConsoleUpdate();

        bool menusShowing = (clientFrontend != null && clientFrontend.menuShowing != ClientFrontend.MenuShowing.None);
        InputSystem.WindowFocusUpdate(menusShowing);

        UpdateCPUStats();
        UpdateEntityStats();

        sqpClient.Update();

        endUpdateEvent?.Invoke();

        /* HACKY WAY TO LOOK AT MONOBEHAVIOURS LEFT
        if (Time.frameCount % 1000 == 100)
        {
            var mbs = FindObjectsOfType(typeof(MonoBehaviour));
            numMBS = mbs.Length;
            var h = new HashSet<Type>();
            foreach (var mb in mbs)
            {
                h.Add(mb.GetType());
            }
            foreach(var t in h)
                GameDebug.Log(":" + t);
            numUMBS = h.Count();
        }
        Overlay.Managed.Write(2, 4, "Monobehaviours left {0} ({1})", numMBS, numUMBS);
        */
    }
    //int numMBS = 0;
    //int numUMBS = 0;

    bool m_ErrorState;

    public void FixedUpdate()
    {
        foreach (var gameLoop in m_gameLoops)
        {
            gameLoop.FixedUpdate();
        }
    }

    public void LateUpdate()
    {
        try
        {
            if (!m_ErrorState)
            {
                foreach (var gameLoop in m_gameLoops)
                {
                    gameLoop.LateUpdate();
                }
                Console.ConsoleLateUpdate();
            }
        }
        catch (System.Exception e)
        {
            HandleGameloopException(e);
            throw;
        }

        if (m_GameStatistics != null)
            m_GameStatistics.TickLateUpdate();

        if (m_DebugOverlay != null)
            m_DebugOverlay.TickLateUpdate();

        Unity.DebugDisplay.Overlay.Managed.instance.TickLateUpdate();
    }

    void OnApplicationQuit()
    {
        ShutdownGameLoops();
    }

    void BlackFade(bool enabled)
    {
        if (m_Exposure != null)
            m_Exposure.active = enabled;
    }

    void OnCameraEnabledChanged(Camera camera, bool enabled)
    {
        if (enabled)
            RenderSettings.UpdateCameraSettings(camera);

        var audioListener = camera.GetComponent<AudioListener>();
        if (audioListener != null)
        {
            audioListener.enabled = enabled;
            if (SoundSystem.Instance != null)
                SoundSystem.Instance.SetCurrentListener(enabled ? audioListener : null);
        }

        if(enabled)
            m_ExposureReleaseCount = 10;
    }

    float m_NextCpuProfileTime = 0;
    double m_LastCpuUsage = 0;
    double m_LastCpuUsageUser = 0;
    void UpdateCPUStats()
    {
        if (debugCpuProfile.IntValue > 0)
        {
            if (Time.time > m_NextCpuProfileTime)
            {
                const float interval = 5.0f;
                m_NextCpuProfileTime = Time.time + interval;
                var process = System.Diagnostics.Process.GetCurrentProcess();
                var user = process.UserProcessorTime.TotalMilliseconds;
                var total = process.TotalProcessorTime.TotalMilliseconds;
                float userUsagePct = (float)(user - m_LastCpuUsageUser) / 10.0f / interval;
                float totalUsagePct = (float)(total - m_LastCpuUsage) / 10.0f / interval;
                m_LastCpuUsage = total;
                m_LastCpuUsageUser = user;
                GameDebug.Log(string.Format("CPU Usage {0}% (user: {1}%)", totalUsagePct, userUsagePct));
            }
        }
    }

    void UpdateEntityStats()
    {
        if (showEntities.IntValue <= 0)
            return;

        int y = 10;
        var aw = World.AllWorlds;
        Overlay.Managed.Write(2, y++, "Worlds: {0}", aw.Count);
        foreach(var w in aw)
        {
            Overlay.Managed.Write(3, y++, "{0}: {1} ents  {2} sys", w.Name, w.EntityManager.UniversalQuery.CalculateEntityCountWithoutFiltering(), w.Systems.Count<ComponentSystemBase>());
        }
    }

    public void LoadLevel(string levelname)
    {
        if (!Game.game.levelManager.CanLoadLevel(levelname))
        {
            GameDebug.Log("ERROR : Cannot load level : " + levelname);
            return;
        }

        Game.game.levelManager.LoadLevel(levelname);
    }

    void UnloadLevel()
    {
        // TODO
    }

    void HandleGameloopException(System.Exception e)
    {
        if (debugCatchLoop.IntValue > 0)
        {
            GameDebug.Log("EXCEPTION " + e.Message + "\n" + e.StackTrace);
            Console.SetOpen(true);
            m_ErrorState = true;
        }
    }

    string FindNewFilename(string pattern)
    {
        for (var i = 0; i < 10000; i++)
        {
            var f = string.Format(pattern, i);
            if (System.IO.File.Exists(string.Format(pattern, i)))
                continue;
            return f;
        }
        return null;
    }

    void ShutdownGameLoops()
    {
        foreach (var gameLoop in m_gameLoops)
            gameLoop.Shutdown();

        m_gameLoops.Clear();
    }

    void CmdServe(string[] args)
    {
        RequestGameLoop(typeof(ServerGameLoop) , args);
        Console.s_PendingCommandsWaitForFrames = 1;
    }

    void CmdLoad(string[] args)
    {
        LoadLevel(args[0]);
        Console.SetOpen(false);
    }

    void CmdProfile(string[] args)
    {
        GameDebug.Log("ProfileGameLoop not available");
        return;
        //RequestGameLoop(typeof(ProfileGameLoop), args);
        //Console.s_PendingCommandsWaitForFrames = 1;
    }

    void CmdBoot(string[] args)
    {
        clientFrontend.ShowMenu(ClientFrontend.MenuShowing.None);
        levelManager.UnloadLevel();
        ShutdownGameLoops();
        Console.s_PendingCommandsWaitForFrames = 1;
        Console.SetOpen(true);
    }

    void CmdClient(string[] args)
    {
        RequestGameLoop(typeof(ClientGameLoop), args);
        Console.s_PendingCommandsWaitForFrames = 1;
    }

    void CmdConnect(string[] args)
    {
        // Special hack to allow "connect a.b.c.d" as shorthand
        if (m_gameLoops.Count == 0)
        {
            RequestGameLoop(typeof(ClientGameLoop), args);
            Console.s_PendingCommandsWaitForFrames = 1;
            return;
        }

        ClientGameLoop clientGameLoop = GetGameLoop<ClientGameLoop>();
        ThinClientGameLoop thinClientGameLoop = GetGameLoop<ThinClientGameLoop>();
        if (clientGameLoop != null)
            clientGameLoop.CmdConnect(args);
        else if (thinClientGameLoop != null)
            thinClientGameLoop.CmdConnect(args);
        else
            GameDebug.Log("Cannot connect from current gamemode");
    }

    void CmdThinClient(string[] args)
    {
        RequestGameLoop(typeof(ThinClientGameLoop), args);
        Console.s_PendingCommandsWaitForFrames = 1;
    }

    void CmdQuit(string[] args)
    {
#if UNITY_EDITOR
        EditorApplication.isPlaying = false;
#else
        Application.Quit();
#endif
    }

    void CmdScreenshot(string[] arguments)
    {
        string filename = null;
        var root = System.IO.Path.GetFullPath(".");
        if (arguments.Length == 0)
            filename = FindNewFilename(root + "/screenshot{0}.png");
        else if (arguments.Length == 1)
        {
            var a = arguments[0];
            if (System.IO.Directory.Exists(a))
                filename = FindNewFilename(a + "/screenshot{0}.png");
            else if (!System.IO.File.Exists(a))
                filename = a;
            else
            {
                Console.Write("File " + a + " already exists");
                return;
            }
        }
        if (filename != null)
        {
            GameDebug.Log("Saving screenshot to " + filename);
            Console.SetOpen(false);
            ScreenCapture.CaptureScreenshot(filename);
        }
    }

    public ClientFrontend clientFrontend;
    private void CmdMenu(string[] args)
    {
        float fadeTime = 0.0f;
        ClientFrontend.MenuShowing show = ClientFrontend.MenuShowing.Main;
        if (args.Length > 0)
        {
            if (args[0] == "0")
                show = ClientFrontend.MenuShowing.None;
            else if (args[0] == "2")
                show = ClientFrontend.MenuShowing.Ingame;
        }
        if (args.Length > 1)
        {
            float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out fadeTime);
        }
        clientFrontend.ShowMenu(show, fadeTime);
        Console.SetOpen(false);
    }

    void CmdSaveConfig(string[] arguments)
    {
        ConfigVar.Save(k_UserConfigFilename);
    }

    void CmdLoadConfig(string[] arguments)
    {
        Console.EnqueueCommandNoHistory("exec " + k_UserConfigFilename);
    }

#if UNITY_STANDALONE_WIN
    void CmdWindowPosition(string[] arguments)
    {
        if (arguments.Length == 1)
        {
            string[] cords = arguments[0].Split(',');
            if (cords.Length == 2)
            {
                int x, y;
                var xParsed = int.TryParse(cords[0], out x);
                var yParsed = int.TryParse(cords[1], out y);
                if (xParsed && yParsed)
                {
                    WindowsUtil.SetWindowPosition(x, y);
                    return;
                }
            }
        }
        Console.Write("Usage: windowpos <x,y>");
    }

#endif

    List<Type> m_RequestedGameLoopTypes = new List<System.Type>();
    private List<string[]> m_RequestedGameLoopArguments = new List<string[]>();

    List<IGameLoop> m_gameLoops = new List<IGameLoop>();
    DebugOverlay m_DebugOverlay;

    bool m_isHeadless;
    long m_StopwatchFrequency;
    System.Diagnostics.Stopwatch m_Clock;

    // Global camera handling
    Exposure m_Exposure;
    Volume m_ExposureVolume;
    int m_ExposureReleaseCount;

}

